Skip to content

feat: add cueapi messages command group (messaging primitive — message lifecycle)#29

Merged
govindkavaturi-art merged 1 commit into
mainfrom
feat/cli-messages-command-group
May 4, 2026
Merged

feat: add cueapi messages command group (messaging primitive — message lifecycle)#29
govindkavaturi-art merged 1 commit into
mainfrom
feat/cli-messages-command-group

Conversation

@mikemolinet
Copy link
Copy Markdown
Collaborator

Summary

Second half of the messaging-primitive CLI port. Pairs with PR #28 (cueapi agents command group) to close the entire Messaging primitive gap from cueapi-cli #25's parity-manifest.json.

PR #28 covers identity + inbox + sent. This PR covers the per-message lifecycle (send / get / read / ack).

Commands shipped (4)

Command Endpoint Notes
cueapi messages send POST /v1/messages sender via X-Cueapi-From-Agent header (not body); idempotency via Idempotency-Key header; renders dedup-hit explicitly; surfaces X-CueAPI-Priority-Downgraded:true; helpful 409 copy on Idempotency-Key conflict
cueapi messages get GET /v1/messages/{id} renders metadata + body verbatim (body up to 32KB; callers can pipe / grep)
cueapi messages read POST /v1/messages/{id}/read idempotent on already-read; 409 on terminal state with helpful copy
cueapi messages ack POST /v1/messages/{id}/ack terminal transition

Design notes worth flagging

  • Sender goes via X-Cueapi-From-Agent HEADER, not in the body. This matches the server's contract (app/routers/messages.py reads it as Header(default=None, alias=\"X-Cueapi-From-Agent\")). Pinned by test_messages_send_minimal_body_and_from_header so a refactor putting from in the body would break the test loudly. Without that test, the refactor would technically still work (server returns 400 via Pydantic extra=\"forbid\") but the failure mode would be silent at unit-test time and only loud at integration time.

  • --expects-reply is a flag — only sent when True. Same omit-when-default pattern as --include-deleted (PR feat: add cueapi agents command group (messaging primitive — identity + inbox + sent) #28) and --has-evidence (PR feat: add executions-list filters — outcome-state / result-type / has-evidence / triggered-by #27). Pinned: test_messages_send_omits_expects_reply_when_unset.

  • --priority validated client-side via click.IntRange(1, 5). Bad values fail fast without an HTTP round-trip. Server's bound is the same.

  • --idempotency-key max length 255 enforced client-side with a clean error message before any HTTP call. Matches the server's hard limit (if idempotency_key and len(idempotency_key) > 255: 400).

  • Dedup-hit UI: server returns HTTP 200 (not 201) when an Idempotency-Key + body match an existing message within 24h. The CLI explicitly says "Idempotency-Key dedup hit: existing message returned" so the user doesn't think a fresh send happened. Pinned: test_messages_send_dedup_hit_renders_existing_label.

  • Priority-downgrade signal surfaced: server may downgrade priority>3 → 3 under receiver-pair limits and signals via X-CueAPI-Priority-Downgraded: true. CLI shows this so users adapt without parsing body. Pinned: test_messages_send_priority_downgrade_header_surfaced.

  • 409 Idempotency-Key conflict copy explicitly tells the user what to do: "reuse the original body or change the key." Better than the generic 409 status string.

Tests

18 new (36 → 54 total). Mock-based body / header / status capture, including a fake headers field on _FakeResp so the priority-downgrade test can verify the CLI reads response headers correctly. Pinned behaviors:

  • --from lands in X-Cueapi-From-Agent header, NOT body
  • --expects-reply only sent when True
  • --priority rejected by Click for out-of-range values
  • --idempotency-key length limit enforced client-side
  • Dedup-hit UI on 200 response
  • Priority-downgrade UI when header is set
  • 409 idempotency-key-conflict shows helpful guidance
  • read / ack render delivery_state + timestamp

All 54 pass locally.

No hosted-PR dependency

All 4 endpoints already shipped on prod via Phase 12.1 messaging primitive (per MEMORY.md). Pure CLI catch-up.

Test plan

  • python3 -m pytest tests/test_cli.py -q → 54 passed
  • Manual smoke against staging once both PR feat: add cueapi agents command group (messaging primitive — identity + inbox + sent) #28 and this land:
    • Create two test agents via cueapi agents create
    • cueapi messages send --from <a> --to <b> --body \"hi\" --idempotency-key k1 → 201
    • Re-run same command → 200 + "dedup hit" UI
    • Re-run with different --body + same key → 409 with helpful copy
    • cueapi messages get <id> → renders body + metadata
    • cueapi messages read <id> → 200; second call → 200 unchanged
    • cueapi messages ack <id> → terminal state

Parity Impact

Closes the messages portion of endpoints_missing.Messaging primitive from cueapi-cli #25's parity-manifest.json. Combined with PR #28 (agents portion), the entire messaging-primitive entry is closed. Manifest update follows after #25 merges to main.

Companion PR

#28cueapi agents command group. Both PRs are independent (different cli.py sections, different test classes) and can land in either order.

🤖 Generated with Claude Code

Copy link
Copy Markdown
Member

@govindkavaturi-art govindkavaturi-art left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean port of messages lifecycle (send/get/read/ack). Slug-form addressing handled via --from/--to with X-Cueapi-From-Agent header. --priority clamped via click.IntRange(1, 5) matches server semantics. Approve.

@govindkavaturi-art
Copy link
Copy Markdown
Member

@mikemolinet — this PR is approved but has conflict with main since #28 (cueapi agents command group) just landed. Conflict is in cueapi/cli.py (new command groups added at the end of the file collide) and tests/test_cli.py (test class definitions overlap — _AgentsClient from #28 vs your test client class).

Action: rebase onto main, keep both class definitions / both command groups.

git fetch origin main
git rebase origin/main
# In tests/test_cli.py: keep BOTH _AgentsClient AND your test class
# In cueapi/cli.py: keep BOTH the agents group from #28 AND your additions
git add -u
git rebase --continue
git push --force-with-lease

I'll re-merge as soon as it's green. Sorry for the cascade — it's the cost of merging the agents group first; the rest of your stack just needs a quick rebase. ✅

@mikemolinet mikemolinet force-pushed the feat/cli-messages-command-group branch 5 times, most recently from 258543e to f392148 Compare May 4, 2026 19:17
4 commands. Tests: 18 new (115 → 133 total).
Rebased against main 2026-05-04 (post-#26, #27, #28, #30, #31, #33).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@mikemolinet mikemolinet force-pushed the feat/cli-messages-command-group branch from f392148 to 627bf7c Compare May 4, 2026 19:29
@govindkavaturi-art govindkavaturi-art merged commit dd75c63 into main May 4, 2026
3 checks passed
govindkavaturi-art pushed a commit that referenced this pull request May 6, 2026
…38)

Updates `parity-manifest.json` to reflect the 8-PR parity wave landed today.

`commands_covered` block extended with the new commands (now 35 total):
- 3 new executions subcommands (replay / verification-pending / verify) from PR #31
- 9 new agents commands (messaging primitive identity surface) from PR #28
- 4 new messages commands (messaging primitive lifecycle) from PR #29
- 2 new workers commands (list / delete) from PR #33
- 2 new `key webhook-secret` subcommands from PR #33
- `--send-at` flag on `cueapi fire` from PR #37 (in flight, depends on hosted #618)

`command_drift`:
- `cueapi create`: 6 new flags moved missing → covered (--require-payload-override, --required-keys, --delivery, --alerts, --catch-up, --verification, --on-success-fire). Only `--transport` remains in missing — flagged as refactor scope, not a simple flag-add.
- `cueapi update`: 9 new flags moved missing → covered. missing_flags now empty.
- `cueapi fire`: new entry capturing the 3 covered flags including --send-at.
- `cueapi executions get`: missing_display cleared (PR #589 ported in #26).
- `cueapi executions list`: 4 new filters moved missing → covered (no remaining gap).

`endpoints_missing`:
- All 8 entries from the seed manifest cleared except `POST /v1/worker/heartbeat`.
- That endpoint stays in missing with documented rationale: cueapi-worker is
  the canonical wrapper with proper heartbeat-loop semantics. Direct CLI
  registration is redundant. Decision documented in the manifest entry +
  cueapi workers group docstring.

`ported_pr_history`:
- 9 entries with merge timestamps, replacing the pre-port forward-looking
  notes.
- Each entry cross-references the cueapi-cli PR + the hosted PR (when
  applicable) + the merge timestamp for forensics.

`cli_version_at_audit`: bumped 0.1.x → 0.2.x to reflect the new surface
area.

`last_full_audit`: kept at 2026-05-04 (this update is the post-merge
snapshot of the 2026-05-04 audit, not a fresh audit).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
mikemolinet added a commit that referenced this pull request May 9, 2026
…2 + #683 parity) (#43)

Extends the existing fire command with two flags covering recently-shipped
server features. --send-at was already there from a prior port (#618).

  --exit-criteria (repeatable, max 20)
      §14 work-verification-light required-assertion keys (cueapi #632).
      Receiver MUST report values for every key under outcome.assertions;
      missing keys mark the execution verification_failed. Pass empty
      string '' once to opt out of cue-level required_assertions for this
      fire (server distinguishes [] = explicit opt-out from None = use
      cue-level).

  --idempotency-key (≤256 chars)
      Opaque dedup key (cueapi #683 Phase 2). Same key + same body within
      24h returns the cached execution; same key + different body returns
      409 idempotency_key_conflict.

Implementation pin (caught earlier on cueapi-python #33 + cueapi-mcp #29):
idempotency_key is a BODY field on cues fire, NOT the Idempotency-Key
header. Server's FireRequest schema diverges from the messaging primitive's
header-based idempotency. Help text + load-bearing test pin this so a
future refactor doesn't 'simplify' to header-based (which would silently
not work — server FireRequest is extra='forbid' for unknown body fields,
ignores headers in body parsing).

Tests added (3):
  - test_fire_help: pins all 5 flags appear in --help (--payload-override,
    --merge-strategy, --send-at, --exit-criteria, --idempotency-key)
  - test_fire_exit_criteria_repeatable_via_help: validates click parser
    accepts repeated --exit-criteria flags
  - test_fire_help_pins_idempotency_key_as_body_field: load-bearing pin
    against future header-refactor

185/185 tests pass (was 183; 2 new + parser-validation added inline).

Source: drift audit handoff/cueapi-package-drift-2026-05-06; Backlog
rows "Parity port: PR #632 → cueapi-cli" + "PR #683 not in backlog
(folded in same-day)". Triggered by cueapi-main confirming bandwidth
on cueapi-cli lane this session ([CC-CUEAPI-DOC-DEBT-CLOSED-DISCIPLINE-
CODIFIED]).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants